package com.xwj.redpacket.service.impl;

import com.xwj.redpacket.dto.RedPacketDto;
import com.xwj.redpacket.service.RedPacketService;
import com.xwj.redpacket.service.RedService;
import com.xwj.redpacket.util.RedPacketUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author xwj
 * @date 2020/4/27
 */
@Service
public class RedPacketServiceImpl implements RedPacketService {
    private static final Logger logger = LoggerFactory.getLogger(RedPacketServiceImpl.class);
    private static String keyPrefix = "red:packet:";
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedService redService;

    @Override
    public String sendRedPacket(RedPacketDto redPacketDto) {
        List<Integer> redPacketList = RedPacketUtil.divideRedPacket(redPacketDto.getAmount(), redPacketDto.getTotal());
        // 生成红包唯一标识
        String redPacketFlag = UUID.randomUUID().toString();
        String redId = new StringBuilder(keyPrefix).append(redPacketDto.getUserId()).append(":").append(redPacketFlag).toString();
        // 将生成的红包金额存入redis缓存
        redisTemplate.opsForList().leftPushAll(redId, redPacketList);
        String redTotalKey = redId + ":total";
        // 将红包总数存入redis缓存
        redisTemplate.opsForValue().set(redTotalKey, redPacketDto.getTotal());
        // 异步保存红包记录至mysql数据库
        redService.recordRedPacket(redPacketDto, redId, redPacketList);
        return redId;
    }

    @Override
    public BigDecimal robRedPacket(Long userId, String redId) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // 判断用户是否已经抢过红包
        String userRobRecordKey = redId + ":" + userId + ":rob";
        Object obj = valueOperations.get(userRobRecordKey);
        if (obj != null) {
            return new BigDecimal(obj.toString());
        }
        // 判断redis缓存中红包剩余个数
        boolean res = click(redId);
        if (res) {
            /*=======================不加锁，高并发情况下抢红包异常=======================*/
            // 红包数量>0
            // 从redis缓存的金额列表中获取金额
//            Object value = redisTemplate.opsForList().rightPop(redId);
//            if (value != null) {
//                // 抢到红包
//                // 更新redis缓存中剩余红包数量
//                String redTotalKey = redId + ":total";
//                Integer current = valueOperations.get(redTotalKey) == null ? 0 : (Integer) valueOperations.get(redTotalKey);
//                valueOperations.set(redTotalKey, current - 1);
//                // 红包金额
//                BigDecimal result = new BigDecimal(value.toString()).divide(new BigDecimal("100"));
//                // 异步记录抢红包记录
//                redService.saveRedWarRecord(userId, redId, new BigDecimal(value.toString()));
//                // 将用户抢红包记录缓存至redis，用于表示用户已抢过
//                valueOperations.set(userRobRecordKey, result, 24L, TimeUnit.HOURS);
//                logger.info("用户：{}，抢到红包：{}，抢到钱：{}", userId, redId, result);
//                return result;
//            }

            /*=======================redis原子操作实现分布式锁=======================*/
            final String lockKey = redId + ":" + userId + ":lock";
            // 调用setIfAbsent方法，间接实现了分布式锁
            boolean lock = valueOperations.setIfAbsent(lockKey, redId);
            // 设置锁的过期时间
            redisTemplate.expire(lockKey, 24L, TimeUnit.HOURS);
            try {
                if(lock) {
                    // 从redis缓存的金额列表中获取金额
                    Object value = redisTemplate.opsForList().rightPop(redId);
                    if (value != null) {
                        // 抢到红包
                        // 更新redis缓存中剩余红包数量
                        String redTotalKey = redId + ":total";
                        Integer current = valueOperations.get(redTotalKey) == null ? 0 : (Integer) valueOperations.get(redTotalKey);
                        valueOperations.set(redTotalKey, current - 1);
                        // 红包金额
                        BigDecimal result = new BigDecimal(value.toString()).divide(new BigDecimal("100"));
                        // 异步记录抢红包记录
                        redService.saveRedWarRecord(userId, redId, new BigDecimal(value.toString()));
                        // 将用户抢红包记录缓存至redis，用于表示用户已抢过
                        valueOperations.set(userRobRecordKey, result, 24L, TimeUnit.HOURS);
                        logger.info("用户：{}，抢到红包：{}，抢到钱：{}", userId, redId, result);
                        return result;
                    }
                }
            } catch (Exception e) {
                return null;
            }

        }
        return null;
    }

    /**
     * 判断redis缓存中红包剩余个数
     *
     * @param redId
     * @return
     */
    private boolean click(String redId) {
        String redTotalKey = redId + ":total";
        Object resNum = redisTemplate.opsForValue().get(redTotalKey);
        return resNum != null && Integer.valueOf(resNum.toString()) > 0;
    }
}
